/*
 * @(#)XfdmActiveRules.java        1.0 2000/05/09
 *
 * This file is part of Xfuzzy 3.0, a design environment for fuzzy logic
 * based systems.
 *
 * (c) 2000 IMSE-CNM. The authors may be contacted by the email address:
 *                    xfuzzy-team@imse.cnm.es
 *
 * Xfuzzy is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation.
 *
 * Xfuzzy is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

package xfuzzy.xfdm.model.algorithm;

import xfuzzy.lang.*;
import xfuzzy.xfdm.model.XfdmAlgorithm;
import xfuzzy.xfdm.model.XfdmConfig;
import xfuzzy.xfdm.model.XfdmSystemStyle;
import xfuzzy.xfds.*;
import java.util.Vector;

/**
 * Clase abstracta que describe los m�todos comunes de los algoritmos basados
 * en reglas activas (Wang&Mendel y derivados).
 * 
 * @author Francisco Jos� Moreno Velo
 *
 */
public abstract class XfdmActiveRules extends XfdmAlgorithm {

	//----------------------------------------------------------------------------//
	//                        M�TODOS P�BLICOS ABSTRACTOS                         //
	//----------------------------------------------------------------------------//

	/**
	 * Obtiene el nombre del algoritmo
	 */
	public abstract String toString();

	/**
	 * Representaci�n en el fichero de configuraci�n
	 */
	public abstract String toXML();

	/**
	 * Selecciona las reglas entre las reglas activas
	 */
	public abstract Vector<XfdmPseudoRule> pruneRules(Vector<XfdmPseudoRule> rules,KnowledgeBase base);

	//----------------------------------------------------------------------------//
	//                             M�TODOS P�BLICOS                               //
	//----------------------------------------------------------------------------//

	/**
	 * Indica si el algoritmo necesita que las variables de entrada tengan creadas
	 * las funciones de pertenencia. En caso contrario el algoritmo debe encargarse
	 * de dar contenido a los tipos de las variables de entrada.
	 */
	public boolean needInputMemFuncs() {
		return true;
	}
	
	/**
	 * Indica si el algoritmo necesita que las variables de salida tengan creadas
	 * las funciones de pertenencia. En caso contrario el algoritmo debe encargarse
	 * de dar contenido a los tipos de las variables de salida.
	 */
	public boolean needOutputMemFuncs() {
		return false;
	}

	/**
	 * Indica si el algoritmo necesita que la estructura del sistema est� creada.
	 * Este requisito es propio de los algoritmo que contienen alguna etapa de
	 * optimizaci�n param�trica.
	 * @return
	 */
	public boolean needSystemStructure() {
		return false;
	}
		
	/**
	 * Obtiene el tipo de base de conocimiento que genera el algoritmo
	 */
	public int getKnowledgeBaseKind() {
		return KnowledgeBase.RULE_SET;
	}

	/**
	 * Genera el contenido de la base de conocimiento a partir de los datos
	 */
	public void extractKnowlegde(KnowledgeBase base, XfdmConfig config) {

		Vector<XfdmPseudoRule> activerules = createActiveRules(base,config);

		Vector<XfdmPseudoRule> selectedrules = pruneRules(activerules,base);

		createRules(base,selectedrules,config);
	}

	//----------------------------------------------------------------------------//
	//                             M�TODOS PRIVADOS                               //
	//----------------------------------------------------------------------------//

	/**
	 * Obtiene la lista de (pseudo) reglas activas
	 */
	private Vector<XfdmPseudoRule> createActiveRules(KnowledgeBase base, XfdmConfig config) {
		Variable ivar[] = base.getInputs();
		Variable ovar[] = base.getOutputs();
		Vector<XfdmPseudoRule> pseudorules = new Vector<XfdmPseudoRule>();
		double label[][] = null;
		int numclasses[] = null;
		XfdsDataSet dataset = config.getDataSet();
		
		boolean classif = (config.getSystemStyle().getDefuzMethod()==XfdmSystemStyle.CLASSIFICATION);

		if(classif) {
			label = new double[ovar.length][];
			numclasses = new int[ovar.length];
			XfdsAttribute[] oattr = dataset.getOutputAttributes();
			for(int i=0; i<ovar.length; i++) {
				String[] value = ((XfdsEnumDataType)oattr[i].getType()).getValues();
				TypeFactory.createClasses(ovar[i].getType(),value);

				LinguisticLabel mf[] = ovar[i].getType().getAllMembershipFunctions();
				numclasses[i] = mf.length;
				label[i] = new double[mf.length];
				for(int j=0; j<mf.length; j++) label[i][j] = mf[j].get()[0];
			}
		}

		for(int p=0; p<dataset.input.length; p++) {
			int index[] = new int[ivar.length];
			for(int i=0; i<ivar.length; i++) {
				index[i] = getMFIndex(ivar[i].getType(),dataset.input[p][i]);
			}
			double doa = computeActivationDegree(base,index, dataset.input[p]);
			boolean exists = false;
			for(int i=0; i<pseudorules.size(); i++) {
				XfdmPseudoRule pseudo = (XfdmPseudoRule) pseudorules.elementAt(i);
				if(pseudo.test(index)) {
					if(!classif) pseudo.update(dataset.output[p],doa);
					exists = true;
					break;
				}
			}
			if(!exists) {
				if(classif) {
					XfdmPseudoRule pseudo = new XfdmPseudoRule(index,numclasses);
					pseudorules.addElement(pseudo);
				} else {
					XfdmPseudoRule pseudo = new XfdmPseudoRule(index,ovar.length);
					pseudo.update(dataset.output[p],doa);
					pseudorules.addElement(pseudo);
				} 
			}
		}

		if(classif) {
			for(int p=0; p<dataset.input.length; p++) {
				for(int i=0; i<pseudorules.size(); i++) {
					XfdmPseudoRule pseudo = (XfdmPseudoRule) pseudorules.elementAt(i);
					int index[] = pseudo.getAntecedent();
					double doa = computeActivationDegree(base,index, dataset.input[p]);
					pseudo.update( getLabelIndexes(dataset.output[p],label) ,doa);
				}
			}
		}

		return pseudorules;
	}

	/**
	 * Obtiene los �ndices correspondientes a las clases activas
	 */
	private int[] getLabelIndexes(double[] pt, double[][] label) {
		int index[] = new int[label.length];
		for(int i=0; i<pt.length; i++)
			for(int j=0; j<label[i].length; j++)
				if(label[i][j] == pt[i]) index[i] = j;
		return index;
	}

	/**
	 * Crea las reglas y las a�ade a la base de reglas	
	 */
	private void createRules(KnowledgeBase base, Vector<XfdmPseudoRule> rules, XfdmConfig config) {
		switch(config.getSystemStyle().getDefuzMethod()) {
		case XfdmSystemStyle.CLASSIFICATION:
			createClassificationRules(base,rules);
			break;
		case XfdmSystemStyle.FUZZYMEAN:
			createFuzzyMeanRules(base,rules);
			break;
		case XfdmSystemStyle.WEIGHTED:
			createWeightedFuzzyMeanRules(base,rules);
			break;
		case XfdmSystemStyle.TAKAGI:
			createTakagiSugenoRules(base,rules);
			break;
		}
	}

	/**
	 * Genera las reglas de tipo classificador
	 */
	private void createClassificationRules(KnowledgeBase base, Vector<XfdmPseudoRule> rules) {
		Variable ovar[] = base.getOutputs();

		for(int r=0; r<rules.size(); r++) {
			XfdmPseudoRule pseudo = (XfdmPseudoRule) rules.elementAt(r);
			Rule rule = new Rule(pseudo.createAntecedent(base));
			int clind[] = pseudo.getClassIndexes();
			for(int j=0; j<ovar.length; j++) {
				LinguisticLabel lb[] = ovar[j].getType().getAllMembershipFunctions();
				rule.add(new Conclusion(ovar[j],lb[clind[j]],base));
			}

			((RuleSet) base).addRule(rule);
		}
	}

	/**
	 * Genera las reglas de tipo FuzzyMean
	 */
	private void createFuzzyMeanRules(KnowledgeBase base, Vector<XfdmPseudoRule> rules) {
		Variable ovar[] = base.getOutputs();

		for(int r=0; r<rules.size(); r++) {
			XfdmPseudoRule pseudo = (XfdmPseudoRule) rules.elementAt(r);
			Rule rule = new Rule(pseudo.createAntecedent(base));
			double center[] = pseudo.getCenter();
			for(int j=0; j<ovar.length; j++) {
				ParamMemFunc mf = new xfuzzy.pkg.xfl.mfunc.singleton();
				mf.set("mf"+r, ovar[j].getType().getUniverse());
				mf.set(center[j]);
				try { ovar[j].getType().add(mf); } catch(Exception ex) {}
				rule.add(new Conclusion(ovar[j],mf,base));
			}

			((RuleSet) base).addRule(rule);
		}
	}

	/**
	 * Genera las reglas de tipo WeightedFuzzyMean
	 */
	private void createWeightedFuzzyMeanRules(KnowledgeBase base, Vector<XfdmPseudoRule> rules) {
		Variable ovar[] = base.getOutputs();

		for(int r=0; r<rules.size(); r++) {
			XfdmPseudoRule pseudo = (XfdmPseudoRule) rules.elementAt(r);
			Rule rule = new Rule(pseudo.createAntecedent(base));
			double center[] = pseudo.getCenter();
			double param[] = new double[2];
			for(int j=0; j<ovar.length; j++) {
				Universe u = ovar[j].getType().getUniverse();
				ParamMemFunc mf = new xfuzzy.pkg.xfl.mfunc.bell();
				mf.set("mf"+r, u);
				param[0] = center[j];
				param[1] = (u.max() - u.min())/8;
				try { mf.set(param); ovar[j].getType().add(mf); } catch(Exception ex) {}
				rule.add(new Conclusion(ovar[j],mf,base));
			}

			((RuleSet) base).addRule(rule);
		}
	}

	/**
	 * Genera las reglas de tipo TakagiSugeno 
	 */
	private void createTakagiSugenoRules(KnowledgeBase base, Vector<XfdmPseudoRule> rules) {
		Variable ovar[] = base.getOutputs();
		Variable ivar[] = base.getInputs();

		for(int r=0; r<rules.size(); r++) {
			XfdmPseudoRule pseudo = (XfdmPseudoRule) rules.elementAt(r);
			Rule rule = new Rule(pseudo.createAntecedent(base));
			double center[] = pseudo.getCenter();
			double param[] = new double[ivar.length+1];
			for(int j=0; j<ovar.length; j++) {
				ParamMemFunc mf = new xfuzzy.pkg.xfl.mfunc.parametric();
				mf.set("mf"+r, ovar[j].getType().getUniverse());
				param[0] = center[j];
				try { mf.set(param); ovar[j].getType().add(mf); } catch(Exception ex) {}
				rule.add(new Conclusion(ovar[j],mf,base));
			}

			((RuleSet) base).addRule(rule);
		}
	}
	
	/**
	 * Obtiene el �ndice de la etiqueta m�s activa
	 */
	private int getMFIndex(Type type, double x) {
		LinguisticLabel mf[] = type.getAllMembershipFunctions();
		double max = 0;
		int index = 0;
		for(int i=0; i<mf.length; i++) {
			double degree = mf[i].compute(x);
			if(degree>=max) { max = degree; index = i; }
		}
		return index;
	}

	/**
	 * Calcula el grado de activaci�n de un antecedente	
	 */
	private double computeActivationDegree(KnowledgeBase base, int index[], double value[]) {
		Operatorset opset = base.getOperatorset();
		Variable[] ivar= base.getInputs();
		double degree = 1.0;
		for(int i=0; i<index.length; i++) {
			LinguisticLabel mf[] =  ivar[i].getType().getAllMembershipFunctions();
			double dom = mf[index[i]].compute(value[i]);
			degree = opset.and.compute(degree, dom);
		}
		return degree;
	}

}


